// import { PathLike } from 'node:fs';
import * as path from 'node:path';

import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager';
import { Inject, Injectable, Logger } from '@nestjs/common';
import * as dayjs from 'dayjs';
import * as fse from 'fs-extra';
import * as glob from 'glob';
import * as lodash from 'lodash';
import * as schedule from 'node-schedule';
import pRetry from 'p-retry';

import { DataFactory } from '../core/common/global/dataFactory/dataFactory.service';
import { BiliService } from '../core/pipelines/bili.service';
import { DouyinService } from '../core/pipelines/douyin.service';
import { XhsService } from '../core/pipelines/xhs.service';
import { type IScheDuleRule } from '../interface.d';
import { killChrome } from '../utils';

type IItem = Omit<IScheDuleRule, 'platform'> & {
  platforms: IScheDuleRule['platform'][];
};

type IPlatformToJsonFileNameMap = Map<IScheDuleRule['platform'], `${'b' | 'xhs' | 'douyin'}.json`>;

@Injectable()
export class ScheduleService {
  private readonly logger = new Logger(ScheduleService.name);

  @Inject()
  private globalService: DataFactory;

  @Inject()
  private biliService: BiliService;

  @Inject()
  private xhsService: XhsService;

  @Inject()
  private douyinService: DouyinService;

  @Inject(CACHE_MANAGER)
  private cacheManager: Cache;

  async test() {}

  async getASampleCoursePath(
    folder: IScheDuleRule['folder'],
    platform: IScheDuleRule['platform'],
  ): Promise<string | null> {
    const desktopPath = this.globalService.getDesktopPath();

    const platformToLocalDirMap = new Map<IScheDuleRule['platform'], string>([
      ['bili', 'bili'],
      ['douyin', 'douyin'],
      ['xhs', 'tuwen'],
    ]);

    const pattern = path.join(desktopPath, `t_${folder}`, '*', platformToLocalDirMap.get(platform));

    const keyOfCoursesCache = `${folder}_${platform}_courses`;

    const courses: string[] =
      (await this.cacheManager.get(keyOfCoursesCache)) || glob.sync(pattern, { windowsPathsNoEscape: true });

    await this.cacheManager.set(keyOfCoursesCache, courses);

    if (!courses.length) {
      return null;
    }

    const sampleCoursePath = lodash.sample(courses);

    lodash.remove(courses, n => n == sampleCoursePath);

    await this.cacheManager.set(keyOfCoursesCache, courses);

    return sampleCoursePath;
  }

  async run() {
    this.logger.verbose('启动一把');

    const data = await this.globalService.createRealTimeData();

    console.table(data.map(i => ({ ...i, time: dayjs(i.time).format('YYYY-MM-DD HH:mm:ss') })));

    for (const item of data) {
      const { time, platforms } = <IItem>item;

      schedule.scheduleJob(time, async () => {
        for (const platform of platforms) {
          const rule = <Omit<IScheDuleRule, 'jsonConfigPath'>>{ ...item, platform };

          const logger = this.logger;

          const { folder } = rule;

          const sampleCoursePath = await this.getASampleCoursePath(folder, platform);

          this.logger.verbose(`${folder} ${platform} ${sampleCoursePath}`);

          if (!sampleCoursePath) {
            this.logger.warn(`${folder} ${platform} 跳出本轮循环`);
            continue;
          }

          const platformToJsonFileNameMap: IPlatformToJsonFileNameMap = new Map([
            ['bili', 'b.json'],
            ['douyin', 'douyin.json'],
            ['xhs', 'xhs.json'],
          ]);

          const jsonConfigPath = path.join(sampleCoursePath, platformToJsonFileNameMap.get(platform));

          const runtimeMaterial = { ...rule, jsonConfigPath, sampleCoursePath };

          await pRetry(
            async () => {
              this.logger.log('===================================');
              this.logger.log('===================================');
              this.logger.log('===================================');

              this.logger.verbose(`当前发送：${sampleCoursePath}`);
              await killChrome();

              if (platform == 'xhs') {
                await this.xhsService.run(runtimeMaterial).catch(error => {
                  this.logger.error(error);
                  throw new Error(error);
                });
              }

              if (platform == 'douyin') {
                await this.douyinService.run(runtimeMaterial).catch(error => {
                  this.logger.error(error);
                  throw new Error(error);
                });
              }

              if (platform == 'bili') {
                await this.biliService.run(runtimeMaterial).catch(error => {
                  this.logger.error(error);
                  throw new Error(error);
                });
              }
            },

            {
              retries: 2,

              maxTimeout: 15 * 1000,

              minTimeout: 10 * 1000,

              async onFailedAttempt(error) {
                const { attemptNumber } = error;

                logger.error(`第${attemptNumber}次重试：`);

                logger.error(error.message, error.stack);

                await killChrome();

                const timer = 20;

                console.log(`等待${timer}s，让chrome进程彻底杀死后再继续，准备重试`);

                await new Promise(r => setTimeout(r, timer));
              },
            },
          )
            .then(() => {
              this.logger.warn(`success: !!!${sampleCoursePath}`);

              const desktopPath = this.globalService.getDesktopPath();

              const historyPath = this.globalService.createBackupPath();

              const backupPath = path.join(historyPath, sampleCoursePath.replace(desktopPath, ''));

              fse.moveSync(sampleCoursePath, backupPath, { overwrite: true });
            })
            .catch(error => {
              this.logger.fatal(`fail:${sampleCoursePath}`);
              this.logger.fatal(error);
            })
            .finally(() => {
              this.globalService.clearEmptyCourseDir();
            });
        }
      });
    }
  }
}
